/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/label.c
 *
 * 支持 SELinux 标签（安全上下文）的例程
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include <selinux/label.h>

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_class.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "libpq/auth.h"
#include "libpq/libpq-be.h"
#include "miscadmin.h"
#include "sepgsql.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"

/*
 * 保存的钩子条目（如果堆叠）
 */
static ClientAuthentication_hook_type next_client_auth_hook = NULL;
static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
static fmgr_hook_type next_fmgr_hook = NULL;

/*
 * client_label_*
 *
 * 数据库客户端的安全标签。初始客户端安全标签等于 client_label_peer，可以通过对 sepgsql_setcon() 的一个或多个调用进行更改，并且在受信任的过程执行期间也可以临时覆盖。
 *
 * sepgsql_setcon() 是一个事务感知操作；（子）事务回滚也应回滚当前客户端安全标签。因此，我们使用待处理标签的列表 client_label_pending 来跟踪在（子）事务期间设置的标签。
 */
static char *client_label_peer = NULL;	/* 由 getpeercon(3) 设置 */
static List *client_label_pending = NIL;	/* 由 sepgsql_setcon() 设置的待处理列表 */
static char *client_label_committed = NULL; /* 由 sepgsql_setcon() 设置，并且已提交 */
static char *client_label_func = NULL;	/* 由受信任的过程设置 */

typedef struct
{
	SubTransactionId subid;
	char	   *label;
}			pending_label;

/*
 * sepgsql_get_client_label
 *
 * 返回客户端的当前安全标签。所有代码应该使用此例程获取当前标签，而不是引用上面的 client_label_* 变量。
 */
char * sepgsql_get_client_label(void)
{
	/* 受信任的过程客户端标签覆盖 */
	if (client_label_func)
		return client_label_func;

	/* 未提交的 sepgsql_setcon() 值 */
	if (client_label_pending)
	{
		pending_label *fc_plabel = llast(client_label_pending);

		if (fc_plabel->label)
			return fc_plabel->label;
	}
	else if (client_label_committed)
		return client_label_committed;	/* 由 sepgsql_setcon() 提交的设置 */

	/* 默认标签 */
	Assert(client_label_peer != NULL);
	return client_label_peer;
}

/*
 * sepgsql_set_client_label
 *
 * 此例程尝试切换客户端的当前安全标签，并检查相关权限。提供的新标签应添加到 client_label_pending 列表中，然后在事务提交时保存，以确保事务感知。
 */
static void fc_sepgsql_set_client_label(const char *fc_new_label)
{
	const char *fc_tcontext;
	MemoryContext fc_oldcxt;
	pending_label *fc_plabel;

	/* 如果为 NULL，则重置为初始客户端标签 */
	if (!fc_new_label)
		fc_tcontext = client_label_peer;
	else
	{
		if (security_check_context_raw(fc_new_label) < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_NAME),
					 errmsg("SELinux: invalid security label: \"%s\"",
							fc_new_label)));
		fc_tcontext = fc_new_label;
	}

	/* 检查进程：{setcurrent} 权限。 */
	sepgsql_avc_check_perms_label(sepgsql_get_client_label(),
								  SEPG_CLASS_PROCESS,
								  SEPG_PROCESS__SETCURRENT,
								  NULL,
								  true);
	/* 检查进程：{dyntransition} 权限。 */
	sepgsql_avc_check_perms_label(fc_tcontext,
								  SEPG_CLASS_PROCESS,
								  SEPG_PROCESS__DYNTRANSITION,
								  NULL,
								  true);

	/*
	 * 将提供的新标签附加到待处理列表，直到当前事务提交。
	 */
	fc_oldcxt = MemoryContextSwitchTo(CurTransactionContext);

	fc_plabel = palloc0(sizeof(pending_label));
	fc_plabel->subid = GetCurrentSubTransactionId();
	if (fc_new_label)
		fc_plabel->label = pstrdup(fc_new_label);
	client_label_pending = lappend(client_label_pending, fc_plabel);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * sepgsql_xact_callback
 *
 * 事务提交/中止/准备的回调例程。提交或中止 client_label_pending 列表中的更改。
 */
static void fc_sepgsql_xact_callback(XactEvent fc_event, void *fc_arg)
{
	if (fc_event == XACT_EVENT_COMMIT)
	{
		if (client_label_pending != NIL)
		{
			pending_label *fc_plabel = llast(client_label_pending);
			char	   *fc_new_label;

			if (fc_plabel->label)
				fc_new_label = MemoryContextStrdup(TopMemoryContext,
												fc_plabel->label);
			else
				fc_new_label = NULL;

			if (client_label_committed)
				pfree(client_label_committed);

			client_label_committed = fc_new_label;

			/*
			 * XXX - 注意，client_label_pending 的条目是在
			 * CurTransactionContext 中分配的，因此，所有获取的内存区域应
			 * 隐式释放。
			 */
			client_label_pending = NIL;
		}
	}
	else if (fc_event == XACT_EVENT_ABORT)
		client_label_pending = NIL;
}

/*
 * sepgsql_subxact_callback
 *
 * 子事务开始/中止/提交的回调例程。释放在被中止的子事务中设置的所有安全标签。
 */
static void fc_sepgsql_subxact_callback(SubXactEvent fc_event, SubTransactionId fc_mySubid,
						 SubTransactionId fc_parentSubid, void *fc_arg)
{
	ListCell   *fc_cell;

	if (fc_event == SUBXACT_EVENT_ABORT_SUB)
	{
		foreach(fc_cell, client_label_pending)
		{
			pending_label *fc_plabel = lfirst(fc_cell);

			if (fc_plabel->subid == fc_mySubid)
				client_label_pending
					= foreach_delete_current(client_label_pending, fc_cell);
		}
	}
}

/*
 * sepgsql_client_auth
 *
 * 客户端认证钩子的入口点。
 * 它根据 getpeercon() 切换客户端标签，并根据 GUC 设置切换当前执行模式。
 */
static void fc_sepgsql_client_auth(Port *fc_port, int fc_status)
{
	if (next_client_auth_hook)
		(*next_client_auth_hook) (fc_port, fc_status);

	/*
	 * 如果身份验证失败，提供的套接字应尽快关闭，因此我们不需要在这里做任何事情。
	 */
	if (fc_status != STATUS_OK)
		return;

	/*
	 * 使用 libselinux 的 API 获取对等进程的安全标签。
	 */
	if (getpeercon_raw(fc_port->sock, &client_label_peer) < 0)
		ereport(FATAL,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("SELinux: unable to get peer label: %m")));

	/*
	 * 将当前执行模式从 INTERNAL 切换到 DEFAULT 或
	 * PERMISSIVE。
	 */
	if (sepgsql_get_permissive())
		sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
	else
		sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
}

/*
 * sepgsql_needs_fmgr_hook
 *
 * 它通知核心提供的函数是否是受信任的过程，
 * 如果是，则在函数调用的开始、结束和中止时将调用 sepgsql_fmgr_hook。
 */
static bool fc_sepgsql_needs_fmgr_hook(Oid fc_functionId)
{
	ObjectAddress fc_object;

	if (next_needs_fmgr_hook &&
		(*next_needs_fmgr_hook) (fc_functionId))
		return true;

	/*
	 * SELinux 需要通过 security_definer 包装器调用该函数，
	 * 如果此调用将进行域转换。我们将这些
	 * 函数称为受信任的过程，如果安全策略有一条规则
	 * 在执行时切换客户端的安全标签。
	 */
	if (sepgsql_avc_trusted_proc(fc_functionId) != NULL)
		return true;

	/*
	 * 即使不是受信任的过程，除非客户端具有 db_procedure:{execute} 权限，
	 * 否则该函数不应被内联。请注意，
	 * 它随后由于与 ACL_EXECUTE 相同的原因而实际失败。
	 */
	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	if (!sepgsql_avc_check_perms(&fc_object,
								 SEPG_CLASS_DB_PROCEDURE,
								 SEPG_DB_PROCEDURE__EXECUTE |
								 SEPG_DB_PROCEDURE__ENTRYPOINT,
								 SEPGSQL_AVC_NOAUDIT, false))
		return true;

	return false;
}

/*
 * sepgsql_fmgr_hook
 *
 * 它在执行受信任的过程时切换客户端的安全标签。
 */
static void fc_sepgsql_fmgr_hook(FmgrHookEventType fc_event,
				  FmgrInfo *fc_flinfo, Datum *fc_private)
{
	struct
	{
		char	   *old_label;
		char	   *new_label;
		Datum		next_private;
	}		   *fc_stack;

	switch (fc_event)
	{
		case FHET_START:
			fc_stack = (void *) DatumGetPointer(*fc_private);
			if (!fc_stack)
			{
				MemoryContext fc_oldcxt;

				fc_oldcxt = MemoryContextSwitchTo(fc_flinfo->fn_mcxt);
				fc_stack = palloc(sizeof(*fc_stack));
				fc_stack->old_label = NULL;
				fc_stack->new_label = sepgsql_avc_trusted_proc(fc_flinfo->fn_oid);
				fc_stack->next_private = 0;

				MemoryContextSwitchTo(fc_oldcxt);

				/*
				 * process:transition 在旧标签和新标签之间的权限，
				 * 在用户尝试在执行受信任过程时切换客户端的安全标签。
				 *
				 * 此外，应检查 db_procedure:entrypoint 权限
				 * 此过程是否可以作为受信任过程的入口点。请注意，db_procedure:execute
				 * 权限应单独检查。
				 */
				if (fc_stack->new_label)
				{
					ObjectAddress fc_object;

					fc_object.classId = ProcedureRelationId;
					fc_object.objectId = fc_flinfo->fn_oid;
					fc_object.objectSubId = 0;
					sepgsql_avc_check_perms(&fc_object,
											SEPG_CLASS_DB_PROCEDURE,
											SEPG_DB_PROCEDURE__ENTRYPOINT,
											getObjectDescription(&fc_object, false),
											true);

					sepgsql_avc_check_perms_label(fc_stack->new_label,
												  SEPG_CLASS_PROCESS,
												  SEPG_PROCESS__TRANSITION,
												  NULL, true);
				}
				*fc_private = PointerGetDatum(fc_stack);
			}
			Assert(!fc_stack->old_label);
			if (fc_stack->new_label)
			{
				fc_stack->old_label = client_label_func;
				client_label_func = fc_stack->new_label;
			}
			if (next_fmgr_hook)
				(*next_fmgr_hook) (fc_event, fc_flinfo, &fc_stack->next_private);
			break;

		case FHET_END:
		case FHET_ABORT:
			fc_stack = (void *) DatumGetPointer(*fc_private);

			if (next_fmgr_hook)
				(*next_fmgr_hook) (fc_event, fc_flinfo, &fc_stack->next_private);

			if (fc_stack->new_label)
			{
				client_label_func = fc_stack->old_label;
				fc_stack->old_label = NULL;
			}
			break;

		default:
			elog(ERROR, "unexpected event type: %d", (int) fc_event);
			break;
	}
}

/*
 * sepgsql_init_client_label
 *
 * 初始化客户端安全标签并设置与客户端
 * 标签管理相关的钩子。
 */
void sepgsql_init_client_label(void)
{
	/*
	 * 设置虚拟客户端标签。
	 *
	 * XXX - 注意 PostgreSQL 启动后台工作进程，如
	 * autovacuum，而无需身份验证步骤。因此，我们用 SEPGSQL_MODE_INTERNAL 初始化 sepgsql_mode，
	 * 并用服务器进程的安全上下文初始化 client_label。
	 * 此后，它还会启动用户会话的后台。
	 * 在这种情况下，该进程始终在后认证挂钩上，
	 * 我们可以正确初始化 sepgsql_mode 和 client_label。
	 */
	if (getcon_raw(&client_label_peer) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("SELinux: failed to get server security label: %m")));

	/* 客户端身份验证钩子 */
	next_client_auth_hook = ClientAuthentication_hook;
	ClientAuthentication_hook = fc_sepgsql_client_auth;

	/* 受信任的过程钩子 */
	next_needs_fmgr_hook = needs_fmgr_hook;
	needs_fmgr_hook = fc_sepgsql_needs_fmgr_hook;

	next_fmgr_hook = fmgr_hook;
	fmgr_hook = fc_sepgsql_fmgr_hook;

	/* 事务/子事务回调 */
	RegisterXactCallback(fc_sepgsql_xact_callback, NULL);
	RegisterSubXactCallback(fc_sepgsql_subxact_callback, NULL);
}

/*
 * sepgsql_get_label
 *
 * 它返回指定数据库对象的安全上下文。
 * 如果没有标签或标签不正确，则系统将返回
 * “未标签”标签。
 */
char * sepgsql_get_label(Oid fc_classId, Oid fc_objectId, int32 fc_subId)
{
	ObjectAddress fc_object;
	char	   *fc_label;

	fc_object.classId = fc_classId;
	fc_object.objectId = fc_objectId;
	fc_object.objectSubId = fc_subId;

	fc_label = GetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG);
	if (!fc_label || security_check_context_raw(fc_label))
	{
		char	   *fc_unlabeled;

		if (security_get_initial_context_raw("unlabeled", &fc_unlabeled) < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INTERNAL_ERROR),
					 errmsg("SELinux: failed to get initial security label: %m")));
		PG_TRY();
		{
			fc_label = pstrdup(fc_unlabeled);
		}
		PG_FINALLY();
		{
			freecon(fc_unlabeled);
		}
		PG_END_TRY();
	}
	return fc_label;
}

/*
 * sepgsql_object_relabel
 *
 * 安全标签语句的入口点
 */
void sepgsql_object_relabel(const ObjectAddress *fc_object, const char *fc_seclabel)
{
	/*
	 * 验证所提供的安全标签的格式，如果它是 selinux 的安全
	 * 上下文。
	 */
	if (fc_seclabel &&
		security_check_context_raw(fc_seclabel) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("SELinux: invalid security label: \"%s\"", fc_seclabel)));

	/*
	 * 为每个对象类执行实际的权限检查
	 */
	switch (fc_object->classId)
	{
		case DatabaseRelationId:
			sepgsql_database_relabel(fc_object->objectId, fc_seclabel);
			break;

		case NamespaceRelationId:
			sepgsql_schema_relabel(fc_object->objectId, fc_seclabel);
			break;

		case RelationRelationId:
			if (fc_object->objectSubId == 0)
				sepgsql_relation_relabel(fc_object->objectId,
										 fc_seclabel);
			else
				sepgsql_attribute_relabel(fc_object->objectId,
										  fc_object->objectSubId,
										  fc_seclabel);
			break;

		case ProcedureRelationId:
			sepgsql_proc_relabel(fc_object->objectId, fc_seclabel);
			break;

		default:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("sepgsql provider does not support labels on %s",
							getObjectTypeDescription(fc_object, false))));
			break;
	}
}

/*
 * TEXT sepgsql_getcon(VOID)
 *
 * 它返回客户端的安全标签。
 */
PG_FUNCTION_INFO_V1(sepgsql_getcon);
Datum sepgsql_getcon(PG_FUNCTION_ARGS)
{
	char	   *fc_client_label;

	if (!sepgsql_is_enabled())
		PG_RETURN_NULL();

	fc_client_label = sepgsql_get_client_label();

	PG_RETURN_TEXT_P(cstring_to_text(fc_client_label));
}

/*
 * BOOL sepgsql_setcon(TEXT)
 *
 * 它切换客户端的安全标签。
 */
PG_FUNCTION_INFO_V1(sepgsql_setcon);
Datum sepgsql_setcon(PG_FUNCTION_ARGS)
{
	const char *fc_new_label;

	if (PG_ARGISNULL(0))
		fc_new_label = NULL;
	else
		fc_new_label = TextDatumGetCString(PG_GETARG_DATUM(0));

	fc_sepgsql_set_client_label(fc_new_label);

	PG_RETURN_BOOL(true);
}

/*
 * TEXT sepgsql_mcstrans_in(TEXT)
 *
 * 当 mcstrans 守护进程工作时，它将给定的合格 MLS/MCS 范围转换为原始格式。
 */
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_in);
Datum sepgsql_mcstrans_in(PG_FUNCTION_ARGS)
{
	text	   *fc_label = PG_GETARG_TEXT_PP(0);
	char	   *fc_raw_label;
	char	   *fc_result;

	if (!sepgsql_is_enabled())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("sepgsql is not enabled")));

	if (selinux_trans_to_raw_context(text_to_cstring(fc_label),
									 &fc_raw_label) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("SELinux: could not translate security label: %m")));

	PG_TRY();
	{
		fc_result = pstrdup(fc_raw_label);
	}
	PG_FINALLY();
	{
		freecon(fc_raw_label);
	}
	PG_END_TRY();

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}

/*
 * TEXT sepgsql_mcstrans_out(TEXT)
 *
 * 它将给定的原始 MLS/MCS 范围转换为合格格式
 * 当 mcstrans 守护进程正在工作时。
 */
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_out);
Datum sepgsql_mcstrans_out(PG_FUNCTION_ARGS)
{
	text	   *fc_label = PG_GETARG_TEXT_PP(0);
	char	   *fc_qual_label;
	char	   *fc_result;

	if (!sepgsql_is_enabled())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("sepgsql is not currently enabled")));

	if (selinux_raw_to_trans_context(text_to_cstring(fc_label),
									 &fc_qual_label) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("SELinux: could not translate security label: %m")));

	PG_TRY();
	{
		fc_result = pstrdup(fc_qual_label);
	}
	PG_FINALLY();
	{
		freecon(fc_qual_label);
	}
	PG_END_TRY();

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}

/*
 * quote_object_name
 *
 * 它尝试引用给定的标识符
 */
static char * fc_quote_object_name(const char *fc_src1, const char *fc_src2,
				  const char *fc_src3, const char *fc_src4)
{
	StringInfoData fc_result;
	const char *fc_temp;

	initStringInfo(&fc_result);

	if (fc_src1)
	{
		fc_temp = quote_identifier(fc_src1);
		appendStringInfoString(&fc_result, fc_temp);
		if (fc_src1 != fc_temp)
			pfree((void *) fc_temp);
	}
	if (fc_src2)
	{
		fc_temp = quote_identifier(fc_src2);
		appendStringInfo(&fc_result, ".%s", fc_temp);
		if (fc_src2 != fc_temp)
			pfree((void *) fc_temp);
	}
	if (fc_src3)
	{
		fc_temp = quote_identifier(fc_src3);
		appendStringInfo(&fc_result, ".%s", fc_temp);
		if (fc_src3 != fc_temp)
			pfree((void *) fc_temp);
	}
	if (fc_src4)
	{
		fc_temp = quote_identifier(fc_src4);
		appendStringInfo(&fc_result, ".%s", fc_temp);
		if (fc_src4 != fc_temp)
			pfree((void *) fc_temp);
	}
	return fc_result.data;
}

/*
 * exec_object_restorecon
 *
 * 此例程是由 sepgsql_restorecon 调用的辅助程序；它设置
 * 提供的目录 OID 内数据库对象的初始安全标签。
 */
static void fc_exec_object_restorecon(struct selabel_handle *fc_sehnd, Oid fc_catalogId)
{
	Relation	fc_rel;
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	char	   *fc_database_name = get_database_name(MyDatabaseId);
	char	   *fc_namespace_name;
	Oid			fc_namespace_id;
	char	   *fc_relation_name;

	/*
	 * 打开目标目录。在初始标记期间，我们不希望允许
	 * 其他会话进行可写访问。
	 */
	fc_rel = table_open(fc_catalogId, AccessShareLock);

	fc_sscan = systable_beginscan(fc_rel, InvalidOid, false,
							   NULL, 0, NULL);
	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_sscan)))
	{
		Form_pg_database fc_datForm;
		Form_pg_namespace fc_nspForm;
		Form_pg_class fc_relForm;
		Form_pg_attribute fc_attForm;
		Form_pg_proc fc_proForm;
		char	   *fc_objname;
		int			fc_objtype = 1234;
		ObjectAddress fc_object;
		char	   *fc_context;

		/*
		 * 确定对象名称的方式取决于对象类别。因此，任何
		 * 分支在此设置 `objtype`，`objname` 和 `object`。
		 */
		switch (fc_catalogId)
		{
			case DatabaseRelationId:
				fc_datForm = (Form_pg_database) GETSTRUCT(fc_tuple);

				fc_objtype = SELABEL_DB_DATABASE;

				fc_objname = fc_quote_object_name(NameStr(fc_datForm->datname),
											NULL, NULL, NULL);

				fc_object.classId = DatabaseRelationId;
				fc_object.objectId = fc_datForm->oid;
				fc_object.objectSubId = 0;
				break;

			case NamespaceRelationId:
				fc_nspForm = (Form_pg_namespace) GETSTRUCT(fc_tuple);

				fc_objtype = SELABEL_DB_SCHEMA;

				fc_objname = fc_quote_object_name(fc_database_name,
											NameStr(fc_nspForm->nspname),
											NULL, NULL);

				fc_object.classId = NamespaceRelationId;
				fc_object.objectId = fc_nspForm->oid;
				fc_object.objectSubId = 0;
				break;

			case RelationRelationId:
				fc_relForm = (Form_pg_class) GETSTRUCT(fc_tuple);

				if (fc_relForm->relkind == RELKIND_RELATION ||
					fc_relForm->relkind == RELKIND_PARTITIONED_TABLE)
					fc_objtype = SELABEL_DB_TABLE;
				else if (fc_relForm->relkind == RELKIND_SEQUENCE)
					fc_objtype = SELABEL_DB_SEQUENCE;
				else if (fc_relForm->relkind == RELKIND_VIEW)
					fc_objtype = SELABEL_DB_VIEW;
				else
					continue;	/* 无需分配安全标签 */

				fc_namespace_name = get_namespace_name(fc_relForm->relnamespace);
				fc_objname = fc_quote_object_name(fc_database_name,
											fc_namespace_name,
											NameStr(fc_relForm->relname),
											NULL);
				pfree(fc_namespace_name);

				fc_object.classId = RelationRelationId;
				fc_object.objectId = fc_relForm->oid;
				fc_object.objectSubId = 0;
				break;

			case AttributeRelationId:
				fc_attForm = (Form_pg_attribute) GETSTRUCT(fc_tuple);

				if (get_rel_relkind(fc_attForm->attrelid) != RELKIND_RELATION &&
					get_rel_relkind(fc_attForm->attrelid) != RELKIND_PARTITIONED_TABLE)
					continue;	/* 无需分配安全标签 */

				fc_objtype = SELABEL_DB_COLUMN;

				fc_namespace_id = get_rel_namespace(fc_attForm->attrelid);
				fc_namespace_name = get_namespace_name(fc_namespace_id);
				fc_relation_name = get_rel_name(fc_attForm->attrelid);
				fc_objname = fc_quote_object_name(fc_database_name,
											fc_namespace_name,
											fc_relation_name,
											NameStr(fc_attForm->attname));
				pfree(fc_namespace_name);
				pfree(fc_relation_name);

				fc_object.classId = RelationRelationId;
				fc_object.objectId = fc_attForm->attrelid;
				fc_object.objectSubId = fc_attForm->attnum;
				break;

			case ProcedureRelationId:
				fc_proForm = (Form_pg_proc) GETSTRUCT(fc_tuple);

				fc_objtype = SELABEL_DB_PROCEDURE;

				fc_namespace_name = get_namespace_name(fc_proForm->pronamespace);
				fc_objname = fc_quote_object_name(fc_database_name,
											fc_namespace_name,
											NameStr(fc_proForm->proname),
											NULL);
				pfree(fc_namespace_name);

				fc_object.classId = ProcedureRelationId;
				fc_object.objectId = fc_proForm->oid;
				fc_object.objectSubId = 0;
				break;

			default:
				elog(ERROR, "unexpected catalog id: %u", fc_catalogId);
				fc_objname = NULL; /* 为了让编译器安静 */
				break;
		}

		if (selabel_lookup_raw(fc_sehnd, &fc_context, fc_objname, fc_objtype) == 0)
		{
			PG_TRY();
			{
				/*
				 * 检查 SELinux 权限以重新标记获取的对象，
				 * 然后执行实际的重新标记。
				 */
				sepgsql_object_relabel(&fc_object, fc_context);

				SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_context);
			}
			PG_FINALLY();
			{
				freecon(fc_context);
			}
			PG_END_TRY();
		}
		else if (errno == ENOENT)
			ereport(WARNING,
					(errmsg("SELinux: no initial label assigned for %s (type=%d), skipping",
							fc_objname, fc_objtype)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_INTERNAL_ERROR),
					 errmsg("SELinux: could not determine initial security label for %s (type=%d): %m", fc_objname, fc_objtype)));

		pfree(fc_objname);
	}
	systable_endscan(fc_sscan);

	table_close(fc_rel, NoLock);
}

/*
 * BOOL sepgsql_restorecon(TEXT specfile)
 *
 * 此函数尝试根据系统设置在当前数据库中的所有对象上
 * 分配初始安全标签。
 * 它通常在 initdb 之后由 sepgsql-install 脚本调用，以
 * 分配初始安全标签。
 *
 * 如果 @specfile 不为 NULL，它会使用显式指定的 specfile，
 * 而不是系统默认。
 */
PG_FUNCTION_INFO_V1(sepgsql_restorecon);
Datum sepgsql_restorecon(PG_FUNCTION_ARGS)
{
	struct selabel_handle *fc_sehnd;
	struct selinux_opt fc_seopts;

	/*
	 * 必须在运行平台上启用 SELinux。
	 */
	if (!sepgsql_is_enabled())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("sepgsql is not currently enabled")));

	/*
	 * 检查 DAC 权限。只有超级用户可以设置初始安全
	 * 标签，就像文件系统中的 root 用户一样。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("SELinux: must be superuser to restore initial contexts")));

	/*
	 * 打开 selabel_lookup(3) 相关内容。它提供了一组
	 * 初始安全标签与对象类别/名称之间的映射，基于系统设置。
	 */
	if (PG_ARGISNULL(0))
	{
		fc_seopts.type = SELABEL_OPT_UNUSED;
		fc_seopts.value = NULL;
	}
	else
	{
		fc_seopts.type = SELABEL_OPT_PATH;
		fc_seopts.value = TextDatumGetCString(PG_GETARG_DATUM(0));
	}
	fc_sehnd = selabel_open(SELABEL_CTX_DB, &fc_seopts, 1);
	if (!fc_sehnd)
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("SELinux: failed to initialize labeling handle: %m")));
	PG_TRY();
	{
		fc_exec_object_restorecon(fc_sehnd, DatabaseRelationId);
		fc_exec_object_restorecon(fc_sehnd, NamespaceRelationId);
		fc_exec_object_restorecon(fc_sehnd, RelationRelationId);
		fc_exec_object_restorecon(fc_sehnd, AttributeRelationId);
		fc_exec_object_restorecon(fc_sehnd, ProcedureRelationId);
	}
	PG_FINALLY();
	{
		selabel_close(fc_sehnd);
	}
	PG_END_TRY();

	PG_RETURN_BOOL(true);
}
